package me.itsoo.artemis.framework.repository.mapper;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import me.itsoo.artemis.framework.core.util.ArrayUtils;
import me.itsoo.artemis.framework.core.util.Assert;
import me.itsoo.artemis.framework.core.util.ReflectUtils;
import me.itsoo.artemis.framework.repository.config.Configuration;
import me.itsoo.artemis.framework.repository.config.SqlConfigurable;
import me.itsoo.artemis.framework.repository.interceptor.InterceptorMatchPatterns;
import me.itsoo.artemis.framework.repository.interceptor.Invocation;
import me.itsoo.artemis.framework.repository.interceptor.SqlInterceptor;
import me.itsoo.artemis.framework.repository.interceptor.SqlInterceptorChain;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * SqlMapperProxy
 *
 * @author zxy
 * @version 0.0.1
 * @see me.itsoo.artemis.framework.repository.interceptor.SqlInterceptorChain
 * @since 2023/5/25
 */
@Slf4j
public class SqlMapperProxy implements MethodInterceptor, SqlConfigurable {

    @Getter
    private final Configuration config;

    private final SqlMapper<?> source;

    private final SqlMapper<?> sqlMapper;

    private final SqlInterceptorChain sqlInterceptorChain;

    private SqlMapperProxy(SqlMapper<?> sqlMapper) {
        this.config = sqlMapper.getConfig();
        this.source = sqlMapper;
        this.sqlMapper = (SqlMapper<?>) Enhancer.create(sqlMapper.getClass(), new Class[]{SqlMapper.class}, this);

        this.sqlMapper.setConfig(config);
        this.sqlInterceptorChain = new SqlInterceptorChain(config.getSqlInterceptors());
    }

    /**
     * 包装为拦截器
     *
     * @param sqlMapper SqlMapper
     * @return SqlMapper
     */
    @SuppressWarnings("rawtypes")
    public static SqlMapper wrap(SqlMapper<?> sqlMapper) {
        final SqlMapperProxy proxy = new SqlMapperProxy(sqlMapper);
        return proxy.sqlMapper;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy invoker) throws Throwable {
        Assert.isTypeOf(proxy, SqlMapper.class, "'proxy' must be type of me.itsoo.artemis.framework.repository.mapper.SqlMapper");

        if (IgnoreMethodMatch.isNot(method)) {
            for (SqlInterceptor si = getNext(); Objects.nonNull(si); si = getNext()) {
                final Invocation inv = new Invocation(proxy, method, args, invoker);
                // 仅执行符合规则匹配的拦截器，未配置规则的拦截器不处理
                if (InterceptorMatchPatterns.match(si, inv)) {
                    return si.execute(source.getSqlExecutor(), source.getMd(), inv);
                }
            }

            if (log.isDebugEnabled()) {
                log.debug(invoker.getSignature().toString());
            }
        }
        // source invoke
        return invoker.invokeSuper(proxy, args);
    }

    /**
     * 获取下一个拦截器
     *
     * @return SqlInterceptor
     */
    private SqlInterceptor getNext() {
        return sqlInterceptorChain.getCurr();
    }

    /**
     * 采用 CGLIB 代理，忽略代理的隐式方法
     */
    private static class IgnoreMethodMatch {

        private static final String[] IGNORE_METHOD_ARRAY = ArrayUtils.merge(
                // object
                new String[]{"equals", "toString", "hashCode", "clone", "finalize"},
                // interface
                ReflectUtils.getAllMethodNames(SourceMapper.class),
                ReflectUtils.getAllMethodNames(MapperBoundSqlFactory.class),
                ReflectUtils.getAllMethodNames(SqlConfigurable.class));

        private static final Set<String> IGNORE_METHOD_NAMES = new HashSet<>(Arrays.asList(IGNORE_METHOD_ARRAY));

        private static boolean is(Method method) {
            return IGNORE_METHOD_NAMES.contains(method.getName());
        }

        private static boolean isNot(Method method) {
            return !is(method);
        }
    }
}
